现如今，许多小型计算机(如树莓派)，只有有限的资源。在这样的计算机上运行编译器通常是不可能的，或者需要花费太多时间。因此，编译器的一个常见需求是为不同的CPU架构生成代码。让主机为不同的目标编译可执行文件的整个过程，称为交叉编译。

交叉编译中，涉及到两个系统:主机系统和目标系统。编译器在主机系统上运行，并为目标系统生成代码。为了表示系统，使用了所谓的三元组。这是一个配置字符串，通常由CPU架构、供应商和操作系统组成。此外，有关环境的附加信息经常被添加到配置字符串中。例如，x86\_64-pc-win32三元组用于在64位x86 CPU上运行的Windows系统。CPU架构是x86\_64, pc是通用厂商，win32是操作系统，所有这些部分都用连字符连接起来。在ARMv8 CPU上运行的Linux系统使用aarch64-unknown-linux-gnu作为三元组，使用aarch64作为CPU架构。此外，操作系统是linux，运行gnu环境。没有真正的基于linux的系统供应商，所以这部分是未知的。此外，对于特定目的不知道或不重要的部分经常省略:aarch64-linux-gnu三元组描述相同的Linux系统。

假设开发机器在x86-64位CPU上运行Linux，并且希望交叉编译到运行Linux的ARMv8 CPU系统。主机三元组为x86\_64-linux-gnu，目标三元组为aarch64-linux-gnu。不同的系统有不同的特点，所以应用程序必须以可移植的方式编写;否则，可能会出现一些问题。一些常见的问题如下:

\begin{itemize}
\item
字节顺序:多字节值在内存中的存储顺序可以不同。

\item
指针大小:指针大小随CPU架构不同而不同(通常为16位、32位或64位)。C int类型可能不够大，无法容纳指针。

\item
类型差异:数据类型通常与硬件密切相关。long double类型可以使用64位(ARM)、80位(X86)或128位(ARMv8)。PowerPC系统可以对长双精度使用double-double算法，通过使用两个64位双精度值的组合来提供更高的精度。
\end{itemize}

若不注意这些问题，则即使应用程序在您的主机系统上完美运行，也可能在目标平台上异常运行或崩溃。LLVM库在不同的平台上进行了测试，并且还包含针对上述问题的可移植解决方案。

交叉编译时，需要使用以下工具:

\begin{itemize}
\item
为目标生成代码的编译器

\item
能够为目标生成二进制文件的链接器

\item
目标的头文件和库
\end{itemize}

幸运的是，Ubuntu和Debian发行版都有支持交叉编译的包。我们将在下面的设置中利用这一点。gcc和g++编译器、链接器、ld和库都可以作为预编译的二进制文件使用，它们可以生成ARMv8代码和可执行文件。下面的命令安装所有这些包:

\begin{shell}
$ sudo apt –y install gcc-12-aarch64-linux-gnu \
    g++-12-aarch64-linux-gnu binutils-aarch64-linux-gnu \
    libstdc++-12-dev-arm64-cross
\end{shell}

新文件安装在/usr/aarch64-linux-gnu目录下。该目录是目标系统的(逻辑)根目录，包含通常的bin、lib和include目录，交叉编译器(aarch64-linux-gnu-gcc-8和aarch64-linux-gnu-g++-8)知晓这个目录。

\begin{myTip}{在其他系统上交叉编译}
有些发行版(如Fedora)只提供对裸机目标(如Linux内核)的交叉编译支持，但不提供用户应用程序所需的头文件和库文件，可以从目标系统复制缺失的文件。

若发行版没有附带所需的工具链，可以从源代码构建它。对于编译器，可以使用clang或gcc/g++。gcc和g++编译器必须配置为为目标系统生成代码，binutils工具需要为目标系统处理文件。此外，C和C++库需要使用这个工具链进行编译。步骤因操作系统、主机和目标架构而异。在web上，搜索gcc cross-compile <architecture>，可以找到相关说明。
\end{myTip}

有了这些准备，除了一个小细节之外，可以交叉编译示例应用程序(包括LLVM库)了。LLVM在构建过程中使用TableGen工具。交叉编译期间，将为目标架构编译所有内容，包括此工具。可以使用第1章中构建的llvm-tblgen，也可以只编译这个工具。假设在本书的GitHub代码库克隆的目录中，输入以下命令:

\begin{shell}
$ mkdir build-host
$ cd build-host
$ cmake -G Ninja \
    -DLLVM_TARGETS_TO_BUILD="X86" \
    -DLLVM_ENABLE_ASSERTIONS=ON \
    -DCMAKE_BUILD_TYPE=Release \
    ../llvm-project/llvm
$ ninja llvm-tblgen
$ cd ..
\end{shell}

这些步骤现在应该很熟悉了。创建并输入一个构建目录。cmake命令仅为X86目标创建LLVM的构建文件。为了节省空间和时间，完成了发布构建，启用了断言来捕获可能的错误。只有llvm-tblgen工具是用ninja编译的。

有了llvm-tblgen工具，就可以开始交叉编译过程了。CMake命令行非常长，开发者可能希望将命令存储在脚本文件中。与以前版本的不同之处在于必须提供更多信息:

\begin{shell}
$ mkdir build-target
$ cd build-target
$ cmake -G Ninja \
    -DCMAKE_CROSSCOMPILING=True \
    -DLLVM_TABLEGEN=../build-host/bin/llvm-tblgen \
    -DLLVM_DEFAULT_TARGET_TRIPLE=aarch64-linux-gnu \
    -DLLVM_TARGET_ARCH=AArch64 \
    -DLLVM_TARGETS_TO_BUILD=AArch64 \
    -DLLVM_ENABLE_ASSERTIONS=ON \
    -DLLVM_EXTERNAL_PROJECTS=tinylang \
    -DLLVM_EXTERNAL_TINYLANG_SOURCE_DIR=../tinylang \
    -DCMAKE_INSTALL_PREFIX=../target-tinylang \
    -DCMAKE_BUILD_TYPE=Release \
    -DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc-12 \
    -DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++-12 \
    ../llvm-project/llvm
$ ninja
\end{shell}

同样，需要创建一个构建目录并在运行CMake命令之前输入它。这些CMake参数中有一些以前没有使用过，需要一些解释:

\begin{itemize}
\item
CMAKE\_CROSSCOMPILING设置为ON，说明CMake正在交叉编译。

\item
LLVM\_TABLEGEN指定要使用的llvm-tblgen工具的路径。

\item
LLVM\_DEFAULT\_TARGET\_TRIPLE是目标架构的三元组。

\item
LLVM\_TARGET\_ARCH用于JIT代码生成。默认为主机的架构。对于交叉编译，必须将其设置为目标架构。

\item
LLVM\_TARGETS\_TO\_BUILD是LLVM应该包含代码生成器的目标列表，该列表至少应该包括目标架构。

\item
CMAKE\_C\_COMPILER和CMAKE\_CXX\_COMPILER分别指定用于构建的C和C++编译器。交叉编译器的二进制文件以目标三元组为前缀，并且不会让CMake自动找到。
\end{itemize}

使用其他参数，将请求启用断言的版本构建，并将的tinylang应用程序构建为LLVM的一部分。编译过程完成后，file命令可以证明我们已经为ARMv8创建了一个二进制文件。具体来说，我们可以运行\$ file bin/ tinylang并检查输出是否为用于ARM aarch64架构的ELF 64位对象。

\begin{myTip}{使用clang进行交叉编译}
由于LLVM为不同的架构生成代码，使用clang进行交叉编译似乎显而易见。这里的难点是LLVM不提供所有必需的部分——例如，缺少C库。正因为如此，必须混合使用LLVM和GNU工具，所以需要告诉CMake更多关于正在使用的环境的信息。至少，需要为clang和clang++指定以下选项:-{}-target=<targettriple>(为不同的目标启用代码生成)，-{}-sysroot=<path>(目标的根目录路径)，-I(搜索头文件的路径)和-L(搜索库的路径)。

CMake运行期间，编译一个小的应用程序时，若设置有问题，CMake会报错。这一步足以检查你是否有一个工作环境。常见的问题是由于不同的库名称或错误的搜索路径，导致选择错误的头文件或链接，从而编译失败。
\end{myTip}

交叉编译非常复杂。根据本节的说明，将能够为您选择的架构结构交叉编译应用程序。

























